# coding: utf-8

'''\
Пакет, созданный чтобы максимально упростить работу с многопоточными модулями.
На данный момент содержит пять функций и два базовых класса.

Использование:
    
    Работа патека организованна так, что программисту не нужно обращаться
    к рабочим модулям напрямую. Все самое необходимое сделает за программиста
    пакет. Передначалом работы нужно импортировать его и запустить функцию
    initialize(), которая проведет поиск модулей. Дополнительная информация
    находится в документации к функциям.

Написание собственных модулей:

    Пользовательский модуль должен быть помещен в папку пакета и иметь
    класс, который наследует BasePrice с переопределенным методом run.
    Все ограничения есть в документации к BasePrice.
'''
import os as _os
import imp as _imp
from glob import glob as _glob
from threading import Thread
from threading import Event
#from threading import Lock
from pyshoper.datadefs import Tree
from time import sleep

ext_objects = list()
ext_classes = dict()

def initialize():
    '''
    Функция, которая сканирует папку с модулями на их наличие. Признаком
    того, что найденный python-скрипт есть модуль для прайса, будет класс,
    являющийся подклассом BasePrice.
    Найденные классы будут записанны в ext_classes.'''
    global ext_classes
    directory = _glob(_os.path.dirname(__file__) + '/*.py')
    filename, ext = _os.path.splitext(__file__)
    directory.remove(filename + '.py')
    for each in directory:
        module = _imp.load_source(__name__, each)
        for obj in module.__dict__:
            if (isinstance(module.__dict__[obj], type) and
                issubclass(module.__dict__[obj], BasePrice)
                and module.__dict__[obj] is not BasePrice):
                ext_classes[module.__dict__[obj].__module_name__] =\
                    (module.__dict__[obj])

def wake_all():
    '''
    Функция, которая запускает модули.'''
    global ext_classes, ext_objects
    for (_, each) in ext_classes:
        ext_objects.append(each.main())

def wake_only(name):
    ext_objects.append(ext_classes[name].main())

def wait_all():
    '''
    Функция, которая ожидает завершения работы всех модулей.'''
    global ext_objects
    for each in ext_objects:
        each.done_event.wait()

def ext_empty():
    '''
    Возвращает True, если все модули завершили работу и вернули данные.'''
    global ext_objects
    return not len(ext_objects)

def return_completed():
    '''
    Функция, которая возвращает pyshoper.Tree c данными,
    уже готовыми для занесения в базу.'''
    global ext_objects
    completed = list()
    for each in ext_objects:
        if each.done_event.is_set():
           completed.append(each)
    cache = list()
    for each in completed:
        cache.append(each.tree)
        ext_objects.remove(each)

    root = Tree()
    for each in cache:
        for section in each.getlist():
            rsection = root.getbranch(section.title())
            for category in section.getlist():
                rcategory = rsection.getbranch(category.title())
                rcategory.products.extend(category.products)

    return root
    
class BasePrice(Thread):
    '''
    Основа для всех классов прайсов. Загрузчик модулей будет искать в каждом
    модуле класс, являющийся подклассом BasePrice и путем нехитрых манипуляций
    запускать у него метод run, который описывает поведение.
    main имеют целью вернуть объект BasePrice с уже готовыми данными.
    Все объекты, в свою очередь, сохраняются в переменной cache этого модуля.
    
    Список требований к модулю:
        
        * Является подклассом BasePrice.
        
        * Ни к коем случае не перегружать __init__. Как правило все данные
          для модуля можно без проблем объявить статическими, и функционал
          от этого не пострадает.
        
        * Перегруженный метод run, в котором описанны все действия модуля.
          Внутри модуля крайне не рекомендуется создавать объекты классов
          Section и Category. Для этого есть уже готовый метод set_branch.'''
    
    __module_name__ = 'unnamed'
    __description__ = str()
            
    @classmethod
    def main(cls, *args, **kwargs):
        instance = cls(*args, **kwargs)
        instance_runmethod = instance.run
        def run_function():
            instance_runmethod()
            instance.done_event.set()
        instance.run = run_function
        instance.start()
        return instance
    
    def run(self):
        raise NotImplementedError
    
    def __init__(self):
        super(BasePrice, self).__init__()
        self.done_event = Event()
        self.tree = Tree()
    
    def setbranch(self, title, element=None):
        if not element:
            return self.tree.getbranch(title)
        return element.getbranch(title)
                
    

class MultipleParsers(object):
    '''
    Класс, предоставляющий удобную форму хранения нескольких парсеров в модуле.
    Подкласс MultipleParser должен обязательно преопределить статическую
    переменную __parsers__, иначе все добавленные парсеры будут доступны
    всем остальным модулям (что может обернуться конфликтом имен).
    
    Не забывайте, что парсер принмимает в качестве первого аргумента не
    экземпляр класса, а сам класс. Это сделанно для того, чтобы парсеры
    не могли менять содержимое класса, то есть, в целях безопасности.
    
    Из парсера можно вызывать только класс-методы.
    Вот небольшой пример класса, реализующего MultipleParser:
    
        class A(BasePrice, MultipleParsers):
        
            __module_name__ = 'unnamed'
            __description__ = str()
            __parsers__ = dict()
            
            def run(self):
                print self.wake_parser('parser name')
            
            @classmethod
            def sample_class_method(cls):
                return True
        
        @A.parser('parser name')
        def parser_name(cls):
            return cls.sample_class_method()'''

    __parsers__ = dict()
    
    @classmethod
    def parser(cls, name):
        def decorator(function):
            cls.__parsers__[name] = function
            function.__name__ = name
            return function
        return decorator
    
    @classmethod
    def wake_parser(cls, name, *args, **kwargs):
        parser = cls.__parsers__.get(name,
            lambda cls, *args, **kwargs: NotImplemented)
        return parser(cls, *args, **kwargs)
